import { ForbiddenException, HttpException, HttpStatus, Injectable } from '@nestjs/common'
import { InjectRepository } from '@nestjs/typeorm'
import { User } from './user.entity'
import { In, Repository } from 'typeorm'
import { Logs } from '../logs/logs.entity'
import { Roles } from '../roles/roles.entity'
import { getUserDto } from './dto/get-user.dto'
import { QueryConditionUtils } from '../utils/db.helper'
import * as argon2 from 'argon2'

@Injectable()
export class UserService {
  // @InjectRepository(User):属性装饰器,为User实体创建一个与其交互的数据库仓库(repository:存储库)
  // 这个仓库会提供用于操作数据库的标准方法
  constructor(
    @InjectRepository(User) private readonly userRepository: Repository<User>,
    @InjectRepository(Logs) private readonly logsRepository: Repository<Logs>,
    @InjectRepository(Roles) private readonly rolesRepository: Repository<Roles>
  ) {}
  // 查询所有
  findAll(query: getUserDto) {
    // 对分析的业务进行SQL代码分析
    // 查询所有用户要做分页、排序以及按条件查询：page-页码，limit-每页页数，condition-查询条件(username,role,gender),sort-排序
    /** select * from user u,profile p,role r where u.id = p.uid and u.id = r.uid and...
     * select * from user u left join profile p  on u.id = p.uid left join role r on u.id = r.uid where ....
     * */
    const { limit, page, username, gender, role } = query
    const take = limit || 0

    const skip = ((page ? page : 1) - 1) * take //如limit为10，page为3，则要跳过的条数为减去当前页乘以每页条数
    // console.log('🚀 ~ UserService ~ findAll ~ page:', page)

    // 1.find()方法进行筛选查询
    /*  return this.userRepository.find({
      //表示必须选择对象的哪些属性
      select: { id: true, username: true, profile: { gender: true } },
      // 加载与之相关联的表
      relations: { profile: true, roles: true },
      //条件查询
      where: { username: username, roles: { id: role }, profile: { gender: gender } },
      // 分页
      take,
      skip,
      //排序：ASC：升序
      order: {
        id: 'ASC'
      }
    }) */
    //  2.使用QueryBuilder进行筛选查询
    const queryBuilder = this.userRepository
      .createQueryBuilder('user') //创建一个查询构建器，别名为user
      .leftJoinAndSelect('user.profile', 'profile') //执行内连接,即两表都有的记录,第一个参数：主表与关联表的链接,第二个参数：关联标的别名
      .leftJoinAndSelect('user.roles', 'roles')
    queryBuilder.where(username ? 'user.username = :username' : '1=1', { username })
    const obj = {
      'profile.gender': gender,
      'roles.id': role
    }
    const newQueryBuilder = QueryConditionUtils<User>(queryBuilder, obj)
    //Object.keys(obj) 获取对象的所有键。forEach 遍历每个键 key:属性名，obj[key]：属性值,
    // Object.keys(obj).forEach((key) => {
    //   if (obj[key]) {
    //     // 参考：queryBuilder.andWhere('profile.gender = :gender', { gender })
    //     //[] 使得对象字面量中的属性名可以是动态的，允许你使用变量作为属性名,{ [key]: obj[key]}:此时[key]就等于obj[key]的值了
    //     queryBuilder.andWhere(`${key} = :${key}`, { [key]: obj[key] as string | number })
    //   }
    // })
    /*   if (username) {
      queryBuilder.where('user.username = :username', { username })
    } else {
      queryBuilder.where('1=1') //占位符，表示始终为真，防止报错
    }
    if (gender) {
      queryBuilder.andWhere('profile.gender = :gender', { gender })
    } else {
      queryBuilder.andWhere('1=1')
    }
    if (role) {
      queryBuilder.andWhere('roles.id = :role', { role })
    } else {
      queryBuilder.andWhere('1=1')
    } */
    return newQueryBuilder.orderBy('user.id', 'ASC').take(take).skip(skip).getMany() //从数据库获取多个实体数据
    // return this.userRepository.find()
  }
  // 根据用户名查找用户
  find(username: string) {
    // relations 参数用于查询实体时加载关联的其他实体
    return this.userRepository.findOne({ where: { username }, relations: ['roles', 'roles.menus'] })
  }
  // 根据用户id查找用户
  findId(id: number) {
    return this.userRepository.findOne({ where: { id } })
  }
  // 根据用户名查找用户
  // findUser(username: string) {
  //   return this.userRepository.findOne({ where: { username } })
  // }
  // 创建用户

  async create(user: Partial<User>) {
    // console.log('🚀 ~ UserService ~ create ~ user:', user)
    // 如果用户没有提供role，给它一个默认的role:普通用户
    if (!user.roles || user.roles.length === 0) {
      const role = await this.rolesRepository.findOne({ where: { id: 2 } })
      user.roles = role ? [role] : []
    }
    // console.log('🚀 ~ UserService ~ create ~ user:', user)

    // 定义MySQLError错误接口
    // interface MySqlError extends Error {
    //   errno?: number
    //   sqlMessage?: string
    // }
    // typeof 主要用于判断基本数据类型和函数类型，尤其适用于判断 string、number、boolean 等。
    // instanceof 用来判断对象是否为某个特定类或构造函数的实例，主要用于对象和继承关系的判断。
    // 通过前端反映过来的角色id数组进行查询所有用户角色并赋值
    if (user.roles instanceof Array && typeof user.roles[0] === 'number') {
      // 查询所有用户角色
      user.roles = await this.rolesRepository.find({ where: { id: In(user.roles) } })
      // console.log('user.roles', user.roles)
    }
    if (user.username) {
      const User = await this.find(user.username)
      if (User) {
        throw new ForbiddenException('用户已存在，请重新命名')
      }
    }
    //定义user实体类
    const userTmp = this.userRepository.create(user)
    userTmp.password = await argon2.hash(userTmp.password)
    // try {
    const res = await this.userRepository.save(userTmp)
    if (res) {
      return res.username + '注册成功！'
    }
    // } catch (error) {
    //   const err = error as MySqlError
    //   if (err.errno && err?.errno === 1062) {
    //     throw new HttpException(err.sqlMessage + '用户名已存在', 500)
    //   }
    // }
  }

  // 更新:传id以及新的用户数据、Partial<User>:允许我传递部分User类型的字段,User类型的所有字段变成了可选
  async update(id: number, user: Partial<User>) {
    const userTemp = await this.findProfile(id) //获取用户详情
    // 通过前端反映过来的角色id数组进行查询所有用户角色并赋值
    if (user.roles instanceof Array && typeof user.roles[0] === 'number') {
      // 查询所有用户角色
      user.roles = await this.rolesRepository.find({ where: { id: In(user.roles) } })
    }
    if (!userTemp) {
      throw new HttpException('该用户不存在', HttpStatus.NOT_FOUND)
    }
    if (user.username) {
      const User = await this.find(user.username)
      if (User?.id !== +id) {
        if (User) {
          throw new ForbiddenException('用户已存在，请重新命名')
        }
      }
    }
    const newUser = this.userRepository.merge(userTemp, user) //合并两个对象，类似于Object.assign()，将user的属性合并到userTemp中
    // 给密码进行加密
    newUser.password = await argon2.hash(newUser.password)
    // 联合模型更新，需要使用save方法或者queryBuilder
    return this.userRepository.save(newUser)
    // 下面的update方法只适合单模型的更新，不适合有关系的模型更新
    // return this.userRepository.update(id, user)
  }
  // 一对一：获取用户详情
  findProfile(id: number) {
    return this.userRepository.findOne({ where: { id }, relations: { profile: true } })
  }
  // 更新用户密码
  updatepws(id: number, password: Partial<User>) {
    return this.userRepository.update(id, password)
  }
  // 删除
  async remove(id: number) {
    // return this.userRepository.delete(id)
    const user = await this.userRepository.findOne({ where: { id } })
    console.log('🚀 ~ UserService ~ remove ~ user:', user)
    if (!user) {
      throw new HttpException('该用户不存在', 404)
    }
    await this.userRepository.remove(user) //而 remove 方法不接受 null 类型
  }

  // 一对多：获取用户日志
  async findUserLogs(id: number) {
    const user = await this.findId(id)
    if (!user) {
      return { message: '该用户不存在' }
    }
    return this.logsRepository.find({
      where: { user: user.logs }
      // relations 是一个查询选项，用于指定在查询主实体时是否一并加载与该实体相关联的其他实体。这通常用于处理实体之间的关联关系，例如一对一、一对多、多对多等关系。
      // relations: { user: true }
    })
  }
  // 使用使用 QueryBuilder 进行联合查询
  findLogsByGroup(id: number) {
    // 使用SQL语句查询
    // return this.logsRepository.query(
    //   'select logs.result as result,count(logs.result) as count from logs,user where user.id = logs.userId and user.id = 2 group by logs.result order by logs.result desc'
    // )
    // 使用链式查询
    return (
      this.logsRepository
        .createQueryBuilder('logs') //创建QueryBuilder
        .select('logs.result', 'result') //查询logs的result 命名为result
        .addSelect('Count(logs.result)', 'count') //新增查询
        .leftJoinAndSelect('logs.user', 'user') //执行左连接（LEFT JOIN）并选取相关联的 user 数据。logs里有个user属性(相当于userId)
        //条件user表的id，为什么不使用字符串拼接的方式把id:的数据放在里面:防止SQL注入，提高了安全性
        .where('user.id = :id', { id })
        .groupBy('logs.result') //分组查询
        .orderBy('count', 'DESC') //根据count的结果倒序排列
        .addOrderBy('result', 'DESC') //先根据count再根据result排序
        .limit(3) //限制查询结果返回的最大记录数。
        .offset(1) //偏移量,用于指定从查询结果中跳过的记录数，通常与 limit() 一起使用来实现分页查询
        .getRawMany()
      // .getRawMany() 返回数据库查询的原始结果,从数据库直接获取结果，得到普通的js对象或数组,适用于需要高性能和自定义查询结果或者不需要实体类的功能的场景，返回的数据要手动处理之后给前端使用。
      // .getMany() //返回实体对象，适用于需要实体类功能（如验证、方法调用等）的场景。在查询中使用了聚合函数（Count）以及自定义的别名（result, count），所以 getMany() 不适用
    )
  }
  // 多对多：roles角色
  findUserRoles(id: number) {
    return this.rolesRepository.find({
      where: { user: { id } }, //查找与用户id相关的所有角色
      relations: { user: true } //加载与角色相关联的用户
    })
  }
}
